from abc import abstractmethod
from types import FunctionType


class Attribute(object):

    ### INITIALIZER ###

    def __init__(self, section=None):
        assert section in (None, 'patcher', 'box')
        if section is None:
            section = 'box'
        self._section = section
        
    ### SPECIAL METHODS ###

    def __call__(self, wrapped=None):
        if isinstance(wrapped, FunctionType):
            default = wrapped(None)
            doc = wrapped.__doc__
            name = wrapped.__name__
        elif isinstance(wrapped, str):
            default = None
            doc = None
            name = wrapped
        fget = self.get_fget(name, default)
        fset = self.get_fset(name)
        return property(fget, fset, doc=doc)

    ### READ-ONLY PUBLIC PROPERTIES ###

    @property
    def section(self):
        return self._section

    ### PUBLIC METHODS ###

    def get_fget(self, name, default):
        input_massage = self.get_input_massage()
        output_massage = self.get_output_massage()
        predicate = self.get_input_predicate()
        section = self.section
        def fget(self):
            attrs = getattr(self, '_{}_attributes'.format(section))
            if attrs.get(name) is None:
                massaged = input_massage(self, default)
                assert predicate(self, massaged)
                attrs[name] = massaged
            return output_massage(self, attrs[name])
        return fget

    def get_fset(self, name):
        section = self.section
        massage = self.get_input_massage()
        predicate = self.get_input_predicate()
        def fset(self, value):
            assert predicate(self, value)
            getattr(self, '_{}_attributes'.format(section))[name] = massage(self, value)
        return fset

    def get_input_massage(self):
        def massage(self, arg):
            return arg
        return massage

    def get_input_predicate(self):
        def predicate(self, arg):
            return True
        return predicate

    def get_output_massage(self):
        def massage(self, arg):
            return arg
        return massage
